from yacf.Backends.Common.Visitors.GenericVisitors import UndefIDFilter,\
    FuncCallFilter, FuncDeclOfNameFilter

from yacf.Tools.Tree import NodeNotFound, getFileNode
from yacf.Tools.Declarations import decl_of_id

from yacf.Frontend.FrontendFactory import FrontendFactory
import config

from yacf.Backends.Common.TemplateEngine.TemplateParser import TemplateParser, BackendTemplateParser
from yacf.Frontend.InternalRepr import AstToIR
from yacf.Frontend.C99.C99InternalRepr import C99AstToIR
from yacf.Backends.C99.Writers.C99Writer import CWriter


from yacf.Frontend.Shortcuts import getCurrentLanguageAst
c_ast = getCurrentLanguageAst()
from yacf.Frontend.SymbolTable import getSymbolTableFromNode


class MutatorException(Exception):
    """ Exception during mutation

    """
    def __init__(self, description):
        self.description = description 
  
    def __str__(self):
        return self.description

class IgnoreMutationException(MutatorException):
    """ Exception raised when, for some reason, we need to stop a mutation but it is not an error 
        
    """
    pass

class AbortMutationException(MutatorException):
    """ Error during mutation, cannot continue
    """
    pass



class AbstractMutator(object):
    """ Abstract class representing a mutation.


    """
    def __init__(self, backend_name = "Abstract"):
        self._backend_name = backend_name

    def filter(self, ast):
        """ Calls to a simple filter """
        pass

    def mutatorFunction(self, ast):
        """ Mutates the AST

             :return: Starting point of the mutation
        """
        return ast

    def fillTemplate(self, template_code, subs_dir, name = None, show = False):
        """ Replace the substitutions of a template code using a dictionary specified

            :param template_code: String with the source code
            :param subs_dir: Dictionary with substitutions
            :param name: Name of the template to be parsed (debugging)
            :param show: Show the template after variable substitution (debugging)
            :return: New template code 

        """
        if subs_dir:
            template_code = TemplateParser(template_code).render_unicode(**subs_dir)
            if show:
                print " Template " + str(template_code)

        return template_code;



    def parse_snippet(self, template_code, subs_dir, name, show = False, languages = None, writer = CWriter):
        """ Transform a template code snippet to the IR

            :param template_code: String with the source code
            :param subs_dir: Dictionary with substitutions
            :param name: Name of the template to be parsed (debugging)
            :param show: Show the template after variable substitution (debugging)
            :return: IR-1 representation of the snippet

        """
        subtree = None
        if subs_dir:
            template_code = BackendTemplateParser(self._backend_name, template_code).render(**subs_dir)
            if show:
                print " Template " + str(template_code)
        try:
            if not languages:
                languages = config.CURRENT_LANGUAGES
            [subtree_ast, prepro_class, lexer_class, parser_class,] = \
                FrontendFactory().parse_with_language_list(template_code, name, languages, includes = [] )
            # Transform the C99 ast into the internal representation
            migrator = C99AstToIR(Writer = writer, ast = subtree_ast)
            migrator.annotate()
            subtree_IR = migrator.ast
        except IOError:
                print "Pipe Error"
                return None

        return subtree_IR;

    def _get_dict_from_clauses(self, clauses, ast, init = None):
        """ Return a dict of clauses from a list of OmpClause objects
            This method is mantained only for compatibility with
            old-fashion backends (like MPI and/or pure Cuda)

              Example: [OmpClause('REDUCTION', ...), OmpClause('PRIVATE', ...)]
                 will return:  {'REDUCTION' : [....] , 'PRIVATE' : [...]}

            :return: dict with clauses

        """
        clause_names = ['SHARED', 'PRIVATE', 'NOWAIT', 'REDUCTION', 'COPY_IN', 'COPY_OUT', 'COLLAPSE', 'LASTPRIVATE', 'FIRSTPRIVATE']
        clause_dict = {}
        if not init:
            clause_dict = self._clauses
        # Note: Each identifiers is a ParamList
        for elem in clauses:

            if not clause_dict.has_key(elem.name):
                    clause_dict[elem.name] = []
            # Clauses with declarations
            if elem.name in ['SHARED', 'PRIVATE', 'REDUCTION', 'COPY_IN', 'COPY_OUT', 'LASTPRIVATE', 'FIRSTPRIVATE']:
                for id in elem.identifiers.params:
                    decl = decl_of_id(id, ast)
                    if not decl:
                        raise AbortMutationException(" Declaration of " + id.name + " in " + elem.name + " clause could not be found ")
                    # If a declaration with the same name is already stored, pass. Otherwise, append it to the list
                    for stored_decl in clause_dict[elem.name]:
                        if decl.name == stored_decl.name:
                            break
                    else:
                        clause_dict[elem.name].append(decl)
            elif elem.name == 'NOWAIT':
                clause_dict[elem.name] = True
            # Currently, number of COLLAPSE is ignored, and two is assumed
            elif elem.name == 'COLLAPSE':
                clause_dict[elem.name] = True

 
        for name in clause_names:
            if not clause_dict.has_key(name):
                clause_dict[name] = []

        if not init:
            self._clauses = clause_dict
        return  clause_dict

    def _getUndeclVars(self, target_node):
        """ Return a list of variables used but not declared
            on the current subtree
        """
       
        nondecls = {}
        for elem in UndefIDFilter(container_node = target_node).dfs_iter(target_node):
            nondecls[elem.name] = elem
      
        return nondecls.values()
    
    def _getDeclList(self, node_list):
        """ Return a list of variables used but not declared
            on the current subtree
        """
        # from yacf.Frontend.C import c_ast
        from yacf.Frontend.SymbolTable import getSymbolTableFromNode
        # Get the symbol table from any node (they share the same FileAST)
        st = getSymbolTableFromNode(node_list[0])
        decls = []
        for node in node_list:    
                symbol = st.lookUp(node)
                decls += [symbol.node]
 
  
        return decls
    
    def _getFuncCallList(self, node_ast):
        """ Return a list of function calls inside a node_ast """
        calls = {}
        for elem in FuncCallFilter().iterate(node_ast):
            if not calls.has_key(elem.name.name):
                calls[elem.name.name] = elem
        return calls.values()

    
    def _declToParam (self, node):
        """ Create a param node from a variable declaration
            Note: Currently this only makes a pointer from the node
        """
        param = node.__copy__()
        from yacf.Backends.Common.Mutators.AstSupport import PointerMutator
        PointerMutator().apply(param)
        return param

    def apply(self, ast, mutator_opt_arg = None):
        """ Apply a mutation 
    
            :return: filtered node
        """
        start_node = None
        self.ast = ast
        try:
          start_node = self.filter(self.ast)
          if mutator_opt_arg:
              self.mutatorFunction(start_node, mutator_opt_arg)
          else:
              self.mutatorFunction(start_node)
        except NodeNotFound as nf:
            print str(nf)
        return start_node
 
    def filter_iterator(self, ast):
        """ Calls an iterable filter

        """
        raise NotImplemented

    def apply_all(self, ast, mutator_opt_arg = None):
        """ Apply mutation to all matches 

             :return: pointer to last applied mutation
        """
        start_node = None
        self.ast = ast
        try:
            for elem in self.filter_iterator(ast):
                if mutator_opt_arg:
                    start_node = self.mutatorFunction(elem, mutator_opt_arg)
                else:
                    start_node = self.mutatorFunction(elem)
        except NodeNotFound as nf:
            print str(nf)
        return start_node

    def fast_filter(self, ast):
        raise NotImplemented


    def fast_apply_all(self, ast):
        """ Apply mutation to all matches ignoring syntactic order


            :return: pointer to last applied mutation
        """
        start_node = None
        self.ast = ast # WTF?
        for elem in self.fast_filter(ast):
          start_node = self.mutatorFunction(elem)
        return start_node

